/* Copyright 2012 Dustin Mitchell <dmmitche [at] gmail [dot] com>
 *
 * Licensed under the GPLv3.  See included LICENSE.txt for full details.
 */

#include "sync_queue.h"
#include "log.h"

int sync_queue_create (struct sync_queue *queue)
{
  if (!queue)
    return -1;

  queue->n_queued = 0;

  if (pthread_mutex_init (&queue->sync, NULL) != 0) {
    log (LOG_ERR, "unable to create mutex");
    goto err_out1;
  }

  if (pthread_cond_init (&queue->cond, NULL) != 0) {
    log (LOG_ERR, "unable to create condition variable");
    goto err_out2;
  }

  if (list_create (&queue->queue) == -1) {
    log (LOG_ERR, "unable to create list");
    goto err_out3;
  }

  return 0;

err_out3:
  pthread_cond_destroy (&queue->cond);
err_out2:
  pthread_mutex_destroy (&queue->sync);
err_out1:
  return -1;
}

void sync_queue_destroy (struct sync_queue *queue)
{
  if (!queue)
    return;

  list_destroy (&queue->queue);
  pthread_mutex_destroy (&queue->sync);
}

int sync_queue_insert (struct sync_queue *queue, void *data)
{
  if (!queue)
    return -1;

  if (pthread_mutex_lock (&queue->sync) != 0) {
    log (LOG_ERR, "unable to lock mutex");
    return -1;
  }

  if (list_push_back (&queue->queue, data) == -1) {
    log (LOG_ERR, "unable to push to list");
    pthread_mutex_unlock (&queue->sync);
    return -1;
  }

  ++queue->n_queued;

  pthread_mutex_unlock (&queue->sync);
  pthread_cond_signal (&queue->cond);

  return 0;
}

void * sync_queue_remove (struct sync_queue *queue)
{
  void *ret = NULL;

  if (!queue)
    return NULL;

  if (pthread_mutex_lock (&queue->sync) != 0) {
    log (LOG_ERR, "unable to lock mutex");
    return NULL;
  }

  while (!queue->n_queued) {
    if (pthread_cond_wait (&queue->cond, &queue->sync) != 0) {
      log (LOG_ERR, "unable to wait on condition variable");
      return NULL;
    }
  }

  ret = list_front (&queue->queue);
  list_pop_front (&queue->queue);
  --queue->n_queued;

  pthread_mutex_unlock (&queue->sync);

  return ret;
}

void * sync_queue_tryremove (struct sync_queue *queue, int *status)
{
  void *ret = NULL;

  if (!queue)
    return NULL;

  if (status)
    *status = 0;

  if (pthread_mutex_lock (&queue->sync) != 0) {
    log (LOG_ERR, "unable to lock mutex");
    return NULL;
  }

  if (queue->n_queued) {
    if (status)
      *status = 1;

    ret = list_front (&queue->queue);
    list_pop_front (&queue->queue);
    --queue->n_queued;
  }

  pthread_mutex_unlock (&queue->sync);

  return ret;
}
